package beans.factorys.xml;

import beans.BeanDefinition;
import beans.BeanDefinitionStoreException;
import beans.factorys.BeanDefinitionRegistry;
import beans.factorys.PropertyValue;
import beans.factorys.config.RunTimeBeanReference;
import beans.factorys.config.TypedStringValue;
import beans.factorys.support.GenericBeanDefinition;
import core.io.Resource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import utils.Assert;
import utils.StringUtils;

import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;

/**
 * Created by ZhaiGuaili on 2018/7/4.
 */
public class XmlBeanDefinitionReader {


    private static final String ID_ATTRIBUTE = "id";
    private static final String ClASS_ATTRIBUTE = "class";
    private static final String SCOPE_ATTRIBUTE = "socpe";
    private static final String PROPERTY_ELEMENT = "property";
    private static final String REF_ATTRIBUTE = "ref";
    private static final String VALUE_ATTRIBUTE = "value";
    private static final String NAME_ATTRIBUTE = "name";

    protected  final Log log = LogFactory.getLog(getClass());

     BeanDefinitionRegistry registry;

    public XmlBeanDefinitionReader(BeanDefinitionRegistry beanDefinitionRegistry){
        this.registry = beanDefinitionRegistry;
    }



    public void loadBeanDefinition(Resource resource) {
        Assert.notNull(resource,"resource is null");
        SAXReader saxReader = new SAXReader();
        InputStream is = null;
        try {
            is = resource.getInputStream();
            Document document = saxReader.read(is);
            Element rootElement = document.getRootElement();
            Iterator it = rootElement.elementIterator();
            while (it.hasNext()) {
                Element element = (Element) it.next();
                String id = element.attributeValue(ID_ATTRIBUTE);
                String beanClassName = element.attributeValue(ClASS_ATTRIBUTE);
                BeanDefinition bd = new GenericBeanDefinition(id, beanClassName);
                if(element.attributeValue(SCOPE_ATTRIBUTE) != null){
                    bd.setScope(element.attributeValue(SCOPE_ATTRIBUTE));
                }
                parsePropertyElement(element,bd);
                this.registry.RegistryBeanDefinition(id,bd);
            }
        } catch (Exception e) {
            throw new BeanDefinitionStoreException("IOException parsing XML document from " + resource.getDescribtion() ,e);
        }finally{
            if(is != null){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 验证property属性名字不为空
     * 得到pproperty属性的ref或者value值
     * @param element
     * @param bd
     */
    public void parsePropertyElement(Element element,BeanDefinition bd){
        Iterator iterator = element.elementIterator(PROPERTY_ELEMENT);
        while(iterator.hasNext()){
            Element propertyElement = (Element)iterator.next();
            String propertyName = propertyElement.attributeValue(NAME_ATTRIBUTE);
            if(!StringUtils.hasLength(propertyName)){
              log.fatal("Tag 'property' must have a 'name' attribute");
              return;
            }
            Object valueOrRef = parsePropertyValue(propertyElement,bd,propertyName);
            PropertyValue propertyValue = new PropertyValue(propertyName,valueOrRef);
            bd.getPropertyValues().add(propertyValue);
        }

    }

    public Object parsePropertyValue(Element element,BeanDefinition bd,String propertyName){
        String elementName = (propertyName != null) ?
                "<property> element for property '" + propertyName + "'" :
                "<constructor-arg> element";

        String refType = element.attributeValue(REF_ATTRIBUTE);
        String valueType = element.attributeValue(VALUE_ATTRIBUTE);
        if(StringUtils.hasLength(refType)){
            RunTimeBeanReference reference = new RunTimeBeanReference(refType);
            return reference;
        }else if(StringUtils.hasLength(valueType)){
            TypedStringValue typedStringValue = new TypedStringValue(valueType);
            return typedStringValue;
        }else{
            throw new RuntimeException(elementName + " must specify a ref or value");
        }
    }
}
